# Copyright (C) 2005-2006 Frederic Back <fredericback@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.


import gtk
import os
import sys, traceback
import imp
import gobject
from threading import Thread, currentThread
from textwrap import wrap

from _guicore import PGButler
from _guitools import createLabel, errorMessage
from _options import Options
from _plugins import Argument

class PluginLoader:
    """ Add external scripts to a menu item.
        This class is formally an "Extension".
    """

    uiplaceholder = '<ui>\
                <menubar name="MenuBar">\
                        <menu action="Plugins">\
                            <placeholder name="LoadedPlugins">\
                                <menuitem action="%s"/>\
                            </placeholder>\
                        </menu>\
                    </menubar>\
                </ui>'

    def __init__(self):
        self.pluginObjects = []
        self.actiongroup = gtk.ActionGroup("PluginLoaderActions")
        PGButler.instance.uimanager.insert_action_group(self.actiongroup, 1)
        
        ui = """<ui>
                <menubar name="MenuBar">
                        <menu action="Plugins">
                            <placeholder name="LoadedPlugins" />
                            <separator />
                            <menuitem action="LoadPlugin"/>
                        </menu>
                    </menubar>
                </ui>"""
                
                
        action = gtk.Action('LoadPlugin', _("_Load Plugin"), None, gtk.STOCK_ADD)
        action.connect("activate",self.loadPluginDialog)
        self.actiongroup.add_action( action )

        PGButler.instance.uimanager.add_ui_from_string(ui)
        self.loadDefaultPlugins()
        

    def loadDefaultPlugins(self):

        # add package plugins
        try:
            if os.access(Options.pluginPath, os.R_OK):
                for f in os.listdir(Options.pluginPath):
                    self.loadPlugin(Options.pluginPath+f,True)
        except Exception, e:
            print "Could not load plugins:"
            traceback.print_exc()

        # add user plugins from ~/mezogis/plugins
        try:
            if os.access(Options.configpath+"plugins", os.R_OK): 
                for f in os.listdir(Options.configpath+"plugins"):
                    self.loadPlugin(Options.configpath+"plugins"+os.sep+f,True) 
        except Exception, e:
            print "Could not load plugins:"
            traceback.print_exc()


    def __getUserInput(self, action, plugin):
        """ Called when double-clicking on a plugin.
        Ask the user to enter input data required by the plugin,
        then launch it """

        dialog = gtk.Dialog("Runing Plugin: '%s'" %plugin.name,
            PGButler.instance.win, gtk.DIALOG_DESTROY_WITH_PARENT,
            (gtk.STOCK_CANCEL, 0, gtk.STOCK_OK, 1))
        dialog.set_has_separator(False)

        txt = "\n".join(wrap(plugin.longDescription,70)) 
        dialog.vbox.pack_start(createLabel(txt),False,False,6)
        dialog.vbox.pack_start(gtk.HSeparator(),False,False,6)

        arguments = plugin.getInput()
        box = gtk.Table(3,len(arguments))
        box.set_col_spacings(6)
        box.set_row_spacings(3)
        entrywidgets = []

        # add entries for every argument
        i = -1
        for key, argument in arguments.iteritems():
            i += 1

            # a label with the name of the argument
            t = "\n".join(wrap(key,26))
            label = createLabel(t)
            box.attach(label,0,1,i,i+1)

            # argument entry
            if argument.datatype == Argument.ttable:
                w = gtk.combo_box_new_text()
                for t in PGButler.instance.paneDbinfo.info.tables:
                    if t.schema == "public": w.append_text(t.name)
                    else: w.append_text("%s.%s"%(t.schema,t.name))
            elif argument.datatype == Argument.tfileopen:
                w = gtk.FileChooserButton(key)
                w.set_action(gtk.FILE_CHOOSER_ACTION_OPEN)
            elif argument.datatype == Argument.tfilesave:
                w = gtk.FileChooserButton(key)
                w.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
                w.set_current_name(argument.value)
            else:
                w = gtk.Entry()
                if argument.value: w.set_text(str(argument.value))
            entrywidgets.append(w)
            box.attach(w,1,2,i,i+1)

            # a description
            t = "\n".join(wrap(argument.description,26))
            label = createLabel(t)
            box.attach(label,2,3,i,i+1,gtk.SHRINK|gtk.FILL)
 
        dialog.vbox.pack_start(box,False,False,5)
        dialog.vbox.show_all()

        r = dialog.run()
        # repeat until cancelled or all variables correct
        while r == 1:
            i = 0
            for key, argument in arguments.iteritems():

                # switch argument type
                if argument.datatype == Argument.ttable:
                    t = PGButler.instance.paneDbinfo.info.tables[entrywidgets[i].get_active()]
                    v = t.getSQL()
                elif argument.datatype == Argument.tfileopen or \
                     argument.datatype == Argument.tfilesave:

                    print entrywidgets[i].get_filename()
                    v = entrywidgets[i].get_filename()

                else: 
                    v = entrywidgets[i].get_text()

                try: argument.set(v)
                except ValueError, e:
                    errorMessage('Argument "%s" is not correct'%a,e)
                    r = dialog.run()
                    break
                r = 0
                i += 1
            self.runPlugin(plugin)
        dialog.destroy()


    def __progress(self, plugin, percent):
        if self.thread.isAlive():
            gtk.gdk.threads_enter()
            try: self.progDialog.pbar.set_fraction(percent/100)
            except: pass
            gtk.gdk.threads_leave()


    def __pluginException(self, plugin, exception):
        msg = "An exception occurred while running the plugin '%s'"%plugin.name
        print msg,exception
        if self.thread.isAlive():
            gtk.gdk.threads_enter()
            errorMessage(msg,exception)
            gtk.gdk.threads_leave()
        else:
            errorMessage(msg,exception)
        

    def __checkThread(self,plugin):
        """ Observe the thread. If not active, close dialogue. """
        if self.thread.isAlive(): return True
        print "finished:",self.thread.getName()
        b = gtk.Button(None,gtk.STOCK_CLOSE)
        hb = gtk.HBox()
        hb.pack_end(b,False)
        self.progDialog.vbox.pack_start(hb,False,False)
        hb.show_all()
        b.connect("clicked",self.__closeThread,None,plugin)
        return False

        
    def __closeThread(self, widget, event, plugin):
        plugin.setProgressFunc(None)
        plugin.setErrorFunc(None)
        self.threadDb.close()
        self.threadDb = None

        PGButler.instance.db.close()
        PGButler.instance.db.connect()
        PGButler.instance.reloadTableinfo()
         
        self.progDialog.destroy() # close the dialog

    def runPlugin(self, plugin):
        plugin.setProgressFunc( self.__progress )
        plugin.setErrorFunc( self.__pluginException )

        db = PGButler.instance.db.clone()
        self.threadDb = db # I should change the plugin api instead.

        try: db.connect()
        except Exception, e: errorMessage("Cannot connect plugin '%s' to database."%plugin.name,e)

        # popup a dialog to display the plugin's progress
        self.progDialog = gtk.Window()
        self.progDialog.set_resizable(False)
        self.progDialog.set_title("run plugin")
        self.progDialog.set_transient_for(PGButler.instance.win)
        self.progDialog.set_modal(True)
        self.progDialog.connect("delete-event",self.__closeThread,plugin)
        vb = self.progDialog.vbox = gtk.VBox()
        vb.set_border_width(5)
        vb.pack_start(createLabel("running <b>%s</b>"%plugin.name),False,False) 
        self.progDialog.pbar = gtk.ProgressBar()
        vb.pack_start(self.progDialog.pbar,False,False,5)
        self.progDialog.pbar.set_size_request(200,-1)
        self.progDialog.add(vb)
        self.progDialog.show_all()

        self.thread = Thread(None,plugin.run,"plugin %s"%plugin.name,(db,))
        self.thread.start()
        gobject.timeout_add(100,self.__checkThread,plugin)

    def loadPluginDialog(self, w=None):
        f = gtk.FileChooserDialog("Open Plugin File...",
            PGButler.instance.win.get_toplevel(),gtk.FILE_CHOOSER_ACTION_OPEN,
            (gtk.STOCK_CANCEL,0,gtk.STOCK_OK,1))
        r = f.run()
        f.hide()
        if r == 1: self.loadPlugin(f.get_filename())
        f.destroy()

    def loadPlugin(self, filename, silent = False):
        """ Shush if silent == True """

        path, name = os.path.split(filename)
        name, ext = os.path.splitext(name)
        if ext != ".py": return

        try:
            a = sys.modules[name]
            ok = True
        except KeyError:
            ok = False

        if not ok and not silent:
            print 'Another module named "%s" is already in use.'%name

        fp = None
        try:
            fp, pathname, description = imp.find_module(name, [path])
            if not silent: print 'load "%s"'%pathname
            m = imp.load_module(name, fp, pathname, description)
            plugin = m.register()
            self.pluginObjects.append(plugin)

        except Exception, e:
            if not silent: errorMessage("Could not load plugin %s"%name,e)
            if fp: fp.close()
            traceback.print_exc()
        else:
            action = gtk.Action(name,plugin.name,plugin.shortDescription,None)
            action.connect("activate",self.__getUserInput,plugin)
            self.actiongroup.add_action( action )
            string = PluginLoader.uiplaceholder % name
            plugin.ui_id = PGButler.instance.uimanager.add_ui_from_string(string)

PGButler.instance.register_extension( PluginLoader )
